#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

#include "array.h"
#include "xmalloc.h"


static size_t
calc_size(uint8_t value_power_of_2)
{
  return (exp2(value_power_of_2));
}

static void
calc_next_size(size_t new_size, size_t *size, uint8_t *next_power_of_2)
{
  uint8_t next_size = *next_power_of_2;
  size_t abs_size = 0;

  while ((abs_size = calc_size(next_size++)) < new_size)
  {
  }
  *size = abs_size;
  *next_power_of_2 = next_size;
}

struct array *
array_create(size_t type_size, uint8_t value_power_of_2)
{
  struct array *array_ptr = (struct array *) ncmalloc(1, sizeof(struct array));

  array_ptr->used = 0;
  array_ptr->size = calc_size(value_power_of_2);
  array_ptr->next_power_of_2++;
  array_ptr->type_size = type_size;
  array_ptr->data = (void *) ncmalloc(array_ptr->size, type_size);

  return (array_ptr);
}

struct array *
array_realloc(struct array *array_ptr, size_t new_size)
{
  size_t old_size = 0;

  if (array_ptr->size <= new_size)
  {
    old_size = array_ptr->size;
    calc_next_size(new_size, &array_ptr->size, &array_ptr->next_power_of_2);
    array_ptr->data = (void *) ncrealloc(array_ptr->data, old_size*array_ptr->type_size, array_size_abs(array_ptr));
  }
  array_ptr->used = new_size;
  return (array_ptr);
}

void
array_free(struct array *arr)
{
  if (arr)
  {
    if (arr->data)
    {
      ncfree((void *)arr->data, arr->size*arr->type_size);
    }
    ncfree((void *)arr, sizeof(struct array));
  }
}

__inline__ void
array_set_data(struct array *array_ptr, const void *data, size_t data_len)
{
  array_realloc(array_ptr, data_len);
  memcpy(array_ptr->data, data, data_len*array_ptr->type_size);
}

__inline__ void
array_add_data(struct array *array_ptr, const void *data, size_t data_len)
{
  size_t old_size = array_used_abs(array_ptr);
  array_realloc(array_ptr, (array_ptr->used + data_len));
  memcpy( ((unsigned char *)array_ptr->data + old_size), data, array_ptr->type_size*data_len );
}

__inline__ void
array_delete_data(struct array *array_ptr, size_t index, size_t len)
{
  size_t ids = (array_ptr->type_size * index);
  size_t idl = (array_ptr->type_size * len);
  memmove( ((unsigned char *)array_ptr->data + ids), ((unsigned char *)array_ptr->data + ids + idl), array_used_abs(array_ptr) - (ids + idl) );
  if ( (long int)((long int)array_ptr->used - len) >= 0 )
  {
    array_realloc(array_ptr, (array_ptr->used - len));
  }
}

__inline__ void
array_insert_data(struct array *array_ptr, size_t index, const void *data, size_t data_len)
{
  size_t ids = (array_ptr->type_size * index);
  size_t idl = (array_ptr->type_size * data_len);
  size_t usd = array_used_abs(array_ptr);

  array_realloc(array_ptr, (array_ptr->used + data_len));
  memmove( ((unsigned char *)array_ptr->data + ids + idl), ((unsigned char *)array_ptr->data + ids), usd - ids);
  memcpy( ((unsigned char *)array_ptr->data + ids), data, idl );
}

size_t
array_idx_abs(struct array *array_ptr, size_t index)
{
  return ( (size_t) (index * array_ptr->type_size) );
}

size_t
array_used_abs(struct array *array_ptr)
{
  return ( (size_t) (array_ptr->used * array_ptr->type_size) );
}

size_t
array_size_abs(struct array *array_ptr)
{
  return ( (size_t) (array_ptr->size * array_ptr->type_size) );
}

const void *
array_get_offset(struct array *array_ptr, size_t index)
{
  if (index > array_ptr->used) return (NULL);
  return ( (void*)((unsigned char *)array_ptr->data + array_idx_abs(array_ptr, index)) );
}

const void *
array_last(struct array *array_ptr)
{
  return ( array_get_offset( array_ptr, array_used_abs(array_ptr) ) );
}

#ifdef ENABLE_DEBUG
void
array_print_debug(struct array *array_ptr)
{
  fprintf(stderr,
    "[array usage]\n"
    "  struct ptr  : %p\n"
    "  data ptr    : %p - %p\n"
    "  type size   : %d\n"
    "  max length  : %lu\n"
    "  length      : %lu\n"
    "  size (abs)  : %lu bytes\n"
    "  nextsize    : %lu bytes\n"
    "  used (abs)  : %lu bytes\n",
    (void *)array_ptr, (void *)array_ptr->data, (unsigned char *)array_ptr->data + (unsigned long int)array_size_abs(array_ptr), array_ptr->type_size,
            (unsigned long int)array_ptr->size, (unsigned long int)array_ptr->used,
            (unsigned long int)array_size_abs(array_ptr), (unsigned long int)(calc_size(array_ptr->next_power_of_2)), (unsigned long int)array_used_abs(array_ptr));
}
#endif
